/*
Main program for the virtual memory project.
Make all of your modifications to this file.
You may add or rearrange any code or data as you need.
The header files page_table.h and disk.h explain
how to use the page table and disk interfaces.
*/

#include "page_table.h"
#include "disk.h"
#include "program.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <sys/queue.h>

// the free list for 
struct free_frame {
	int num;
	TAILQ_ENTRY(free_frame) entries;
};
// the busy list for fifo
struct busy_frame {
	int num;
	TAILQ_ENTRY(free_frame) entries;
};

// replacement algorithm pointer type, return the page to kick out
typedef int (*replace_t) ( struct page_table * pt, int page );

// the head of free list
TAILQ_HEAD(, free_frame) freeList_head;
// the head of busy list
TAILQ_HEAD(, busy_frame) busyList_head;

// the pointer to replacement function
replace_t replace_func;

// an inverted table for page table
int* inv_pg_tb;
//typedef struct page_table page_table_t;
struct disk * mydisk;

int fifo_replace(struct page_table *pt,int page)
{
	return 0;
}

int sfifo_replace(struct page_table *pt,int page)
{
	return 0;
}

int custom_replace(struct page_table *pt,int page)
{
	return 0;
}

int rand_replace( struct page_table *pt,int page)
{
  int nfrm;
  nfrm = page_table_get_nframes(pt);
  return rand() % nfrm;
}

void page_fault_handler( struct page_table *pt, int page )
{
  //printf("Page Number:%d\n", page);

  int curFrm;
  int curbits;
  
  int newFrame;
  char * myPM;
  // Get curFrm and curbits associated with the page
  page_table_get_entry(pt,page,&curFrm,&curbits);
  myPM = page_table_get_physmem(pt);
  // if bits are read, make write and return
  if(curbits == PROT_READ ){
    page_table_set_entry(pt, page, curFrm, PROT_READ|PROT_WRITE);
    //page_table_print(pt);
    return ;
  }
  // if bits are none, then we need to put page in memory
  if(curbits == PROT_NONE ){
    // if free list is empty, need to use replacement algorithm to find new frame
    if(TAILQ_EMPTY(&freeList_head)){
      newFrame = replace_func(pt,page);
      // if paging out, write page to disk if dirty
      if(curbits == PROT_WRITE)
	disk_write(mydisk, inv_pg_tb[newFrame], &myPM[newFrame * (PAGE_SIZE)]);
      // change the page table for the evicted one
      page_table_set_entry(pt, inv_pg_tb[newFrame], curFrm, PROT_NONE);

      // change page table for new page
      page_table_set_entry(pt, page, newFrame, PROT_READ);
    }
    else{
      // there's free frame
      // take a page from free list
      struct free_frame * item;
      item = TAILQ_FIRST(&freeList_head);
      TAILQ_REMOVE(&freeList_head, item, entries);
      newFrame = item->num;
      free(item);
    }   
    
    // read new page from disk into new frame
    disk_read(mydisk, page, &myPM[newFrame * (PAGE_SIZE)]);

    // change page table for current page to the new frame
    page_table_set_entry(pt, page, newFrame, PROT_READ);
    inv_pg_tb[newFrame] = page;
  }
  else {
    printf("SHOULDN'T HAPPEN\n");
  }
  //page_table_print(pt);
  return;
    
  //printf("page fault on page #%d\n",page);

}

int main( int argc, char *argv[] )
{
	if(argc!=5) {
		printf("use: virtmem <npages> <nframes> <rand|fifo|custom> <sort|scan|focus>\n");
		return 1;
	}

	// initialization
	int npages = atoi(argv[1]);
	int nframes = atoi(argv[2]);
	inv_pg_tb = malloc(nframes*sizeof(int));
	char rand[] = "rand";
	char fifo[] = "fifo";
	char sfifo[] = "2fifo";
	char cust[] = "custom";
	
	struct free_frame * item;
	TAILQ_INIT(&freeList_head);
	TAILQ_INIT(&busyList_head);
	int i;
	for( i = 0 ; i< nframes; i++){
	  item = malloc(sizeof(*item));
	  if (item == NULL) {
	    perror("malloc failed");
	    exit(EXIT_FAILURE);
	  }
	  
	  item -> num = i;
	  TAILQ_INSERT_TAIL(&freeList_head, item, entries);
	}
	
	if(!strcmp(argv[3], rand)){
	  // assign rand replacement policy, replace is a pointer to the function
  	  srand(1); // set seed, only want to set it once
	  replace_func = rand_replace;
	}else if(!strcmp(argv[3], fifo)){
   	  replace_func = fifo_replace;
	}else if(!strcmp(argv[3], sfifo)){
	  replace_func = sfifo_replace;
	}else if(!strcmp(argv[3], cust)) {
          replace_func = custom_replace;
	}
	
	  
	const char *program = argv[4];

	//struct disk *disk = disk_open("myvirtualdisk",npages);
	mydisk = disk_open("myvirtualdisk",npages);
	if(!mydisk) {
		fprintf(stderr,"couldn't create virtual disk: %s\n",strerror(errno));
		return 1;
	}


	struct page_table *pt = page_table_create( npages, nframes, page_fault_handler );
	if(!pt) {
		fprintf(stderr,"couldn't create page table: %s\n",strerror(errno));
		return 1;
	}

	char *virtmem = page_table_get_virtmem(pt);

	//char *physmem = page_table_get_physmem(pt);

	if(!strcmp(program,"sort")) {
		sort_program(virtmem,npages*PAGE_SIZE);

	} else if(!strcmp(program,"scan")) {
		scan_program(virtmem,npages*PAGE_SIZE);

	} else if(!strcmp(program,"focus")) {
		focus_program(virtmem,npages*PAGE_SIZE);

	} else {
		fprintf(stderr,"unknown program: %s\n",argv[3]);

	}

	page_table_delete(pt);
	disk_close(mydisk);

	return 0;
}
